package org.apache.ocean.solr.util;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.ocean.CLong;

public class Timeout {
	public static final int CANCELLED = 10;
	public static final int OK = 1;
	public static final int NO_TIMEOUT = 20;
	public AtomicInteger status;
	public long startTime;
	public long timeout;
	public Throwable throwable;
	public List<Listener> listeners = new ArrayList<Listener>(5);
	public final static TimerThread TIMER_THREAD = new TimerThread();

	public String toString() {
		return ReflectionToStringBuilder.toString(this);
	}

	public Timeout() {
		this.status = new AtomicInteger(NO_TIMEOUT);
	}
  
	public long getElapsed() {
		return TIMER_THREAD.getTime() - startTime;
	}
	
	public static Timeout FOREVER() {
		return new Timeout(Long.MAX_VALUE);
	}

	public Timeout(long timeout) {
		this.timeout = timeout;
		this.status = new AtomicInteger(OK);
		startTime = TIMER_THREAD.getTime();
	}

	public void notifyListenersCancelled() {
		for (Listener listener : listeners)
			listener.cancelled();
	}

	public static class Listener {
		public void cancelled() {
		}
	}

	public void addListener(Listener listener) {
		listeners.add(listener);
	}

	public long getTimeLeft() {
		return (timeout + startTime) - System.currentTimeMillis();
	}

	public void cancel() {
		cancel(null);
	}

	public void cancel(Throwable throwable) {
		if (status.get() != CANCELLED) {
			this.throwable = throwable;
			status.set(CANCELLED);
		}
		notifyListenersCancelled();
	}

	public boolean notOK() {
		return !isOK();
	}

	public boolean isOK() {
		if (status.get() == NO_TIMEOUT)
			return true;
		if (status.get() == CANCELLED)
			return false;
		long dif = TIMER_THREAD.getTime() - startTime;
		boolean ok = timeout > dif;
		if (!ok)
			status.set(CANCELLED);
		return ok;
	}

	public static class TimeoutException extends Exception {
		private Timeout timeout;

		public TimeoutException(Timeout timeout) {
			this.timeout = timeout;
		}

		public TimeoutException(String message, Timeout timeout) {
			super(message);
			this.timeout = timeout;
		}
	}

	public static class TimeoutRuntimeException extends RuntimeException {
		private Timeout timeout;

		public TimeoutRuntimeException(String message, Timeout timeout) {
			super(message);
			this.timeout = timeout;
		}
	}

	public static class TimerThread extends Thread {
		private final int resolution;
		private volatile long time;

		public TimerThread() {
			this(1);
		}

		public TimerThread(final int resolution) {
			this.resolution = resolution;
			this.setDaemon(true);
		}

		public void run() {
			for (;;) {
				time = System.currentTimeMillis();
				try {
					Thread.sleep(resolution);
				} catch (final InterruptedException e) {
					Thread.currentThread().interrupt();
				}
			}
		}

		public long getTime() {
			return time;
		}
	}
}
